using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Globalization;
using System.Linq;
using System.Windows.Forms;
using Microsoft.WowAddonStudio.FrameXml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization.Support;

namespace Microsoft.WowAddonStudio.FrameXml.Components.TypeConverters
{
    /// <summary>
    /// Provides a type converter to convert string objects to and from other representations.
    /// </summary>
    public class InheritanceTypeConverter<TInheritableObject, TDestinationType> : TypeConverter where TInheritableObject : InheritableObject
    {
        private List<string> values;

        /// <summary>
        /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context.
        /// </summary>
        /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
        /// <param name="sourceType">A <see cref="T:System.Type"/> that represents the type you want to convert from.</param>
        /// <returns>
        /// true if this converter can perform the conversion; otherwise, false.
        /// </returns>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string))
            {
                var destinationTypeConverter = TypeDescriptor.GetConverter(typeof(TDestinationType));
                return destinationTypeConverter.CanConvertFrom(typeof(string));
            }

            return base.CanConvertTo(context, sourceType);
        }

        /// <summary>
        /// Converts the given object to the type of this converter, using the specified context and culture information.
        /// </summary>
        /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
        /// <param name="culture">The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.</param>
        /// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> that represents the converted value.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException">
        /// The conversion cannot be performed.
        /// </exception>
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (value is string)
            {
                var destinationTypeConverter = TypeDescriptor.GetConverter(typeof(TDestinationType));
                return destinationTypeConverter.ConvertFrom(context, culture, value);
            }

            return base.ConvertFrom(context, culture, value);
        }

        /// <summary>
        /// Returns whether this object supports a standard set of values that can be picked from a list, using the specified context.
        /// </summary>
        /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
        /// <returns>
        /// true if <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"/> should be called to find a common set of values the object supports; otherwise, false.
        /// </returns>
        public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
        {
            return true;
        }

        /// <summary>
        /// Returns a collection of standard values for the data type this type converter is designed for when provided with a format context.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.ComponentModel.TypeConverter.StandardValuesCollection"></see> that holds a standard set of valid values, or null if the data type does not support a standard set of values.
        /// </returns>
        /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"></see> that provides a format context that can be used to extract additional information about the environment from which this converter is invoked. This parameter or properties of this parameter can be null. </param>
        public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
        {
            values = new List<string>();
            var frameXmlComponent = context.Instance as IFrameXmlComponent;
            if (frameXmlComponent != null)
            {
                var service = (IDesignerHost)context.GetService(typeof(IDesignerHost));
                if (service != null)
                {
                    //Add empty item first
                    values.Add(String.Empty);

                    //Add existing controls, except the selected
                    AddControls<TInheritableObject>(service.RootComponent as Control, frameXmlComponent);

                    // Retrieve the registered names of the given type
                    var names = new List<string>(FrameXmlDirectory.Default.GetNames<TInheritableObject>());
                    
                    // Remove the actual item name from the names
                    names.Remove(frameXmlComponent.Name);

                    // Add names to list of values
                    values.AddRange(names.OrderBy(name => name));
                }
            }

            return new StandardValuesCollection(values);
        }

        /// <summary>
        /// Inits the controls.
        /// </summary>
        /// <param name="component">The component.</param>
        /// <param name="frameXmlComponent">The frame control.</param>
        protected virtual void AddControls<T>(Control component, IFrameXmlComponent frameXmlComponent) where T : InheritableObject
        {
            if (component == null) return;

            if (component is LayoutFrameControl && frameXmlComponent != component)
            {
                LayoutFrameControl layoutFrame = component as LayoutFrameControl;

                Type layoutFrameType = layoutFrame.SerializationObjectType;

                if (layoutFrameType == typeof(T) && layoutFrame.LayoutFrame.Virtual)
                {
                    values.Add(component.Name);
                }
            }

            if (component.Controls.Count > 0)
            {
                foreach (Control control in component.Controls)
                    AddControls<T>(control, frameXmlComponent);
            }
        }
    }
}